#include "Camera.h"

// lib
#include <glm/gtc/matrix_transform.hpp>

Camera::Camera(Camera &camera)
    : m_position(camera.m_position), m_rotation(camera.m_rotation),
      m_FOV(camera.m_FOV), m_aspectRatio(camera.m_aspectRatio),
      m_nearClip(camera.m_nearClip), m_farClip(camera.m_farClip)
{
  RecalculateViewMatrix();
  RecalculateProjectionMatrix();
}

Camera::Camera(float fov, float aspectRatio, float nearClip, float farClip)
    : m_position(0.0f), m_rotation(0.0f), m_FOV(fov), m_aspectRatio(aspectRatio), m_nearClip(nearClip), m_farClip(farClip)
{
  RecalculateViewMatrix();
  RecalculateProjectionMatrix();
}

Ref<Camera> Camera::CreateDefault()
{
  return Ref<Camera>(new Camera(80.0f, 16.0f / 9.0f, 0.1f, 1000.0f));
}

void Camera::SetPosition(const glm::vec3 &position)
{
  m_position = position;
  RecalculateViewMatrix();
}

void Camera::SetRotation(const glm::vec3 &rotation)
{
  m_rotation = rotation;
  RecalculateViewMatrix();
}

void Camera::SetAspectRatio(float aspectRatio)
{
  m_aspectRatio = aspectRatio;
  RecalculateProjectionMatrix();
}

void Camera::SetFov(float fov)
{
  m_FOV = fov;
  RecalculateProjectionMatrix();
}

void Camera::SetZNear(float nearClip)
{
  m_nearClip = nearClip;
  RecalculateProjectionMatrix();
}

void Camera::SetZFar(float farClip)
{
  m_farClip = farClip;
  RecalculateProjectionMatrix();
}

void Camera::RecalculateViewMatrix()
{
  const float c3 = glm::cos(m_rotation.z);
  const float s3 = glm::sin(m_rotation.z);
  const float c2 = glm::cos(m_rotation.x);
  const float s2 = glm::sin(m_rotation.x);
  const float c1 = glm::cos(m_rotation.y);
  const float s1 = glm::sin(m_rotation.y);
  const glm::vec3 u{(c1 * c3 + s1 * s2 * s3), (c2 * s3), (c1 * s2 * s3 - c3 * s1)};
  const glm::vec3 v{(c3 * s1 * s2 - c1 * s3), (c2 * c3), (c1 * c3 * s2 + s1 * s3)};
  const glm::vec3 w{(c2 * s1), (-s2), (c1 * c2)};

  /*
    [u.x,   u.y,  u.z, -dot(u, position)]
    [v.x,   v.y,  v.z, -dot(v, position)]
    [w.x,   w.y,  w.z, -dot(w, position)]
    [0,     0,    0,    1               ]
  */
  m_viewMatrix = glm::mat4{1.f};
  m_viewMatrix[0][0] = u.x;
  m_viewMatrix[1][0] = u.y;
  m_viewMatrix[2][0] = u.z;
  m_viewMatrix[0][1] = v.x;
  m_viewMatrix[1][1] = v.y;
  m_viewMatrix[2][1] = v.z;
  m_viewMatrix[0][2] = w.x;
  m_viewMatrix[1][2] = w.y;
  m_viewMatrix[2][2] = w.z;
  m_viewMatrix[3][0] = -glm::dot(u, m_position);
  m_viewMatrix[3][1] = -glm::dot(v, m_position);
  m_viewMatrix[3][2] = -glm::dot(w, m_position);

  m_viewProjectionMatrix = m_projectionMatrix * m_viewMatrix;

  m_forward = glm::vec3{m_viewMatrix[2][0], m_viewMatrix[2][1], m_viewMatrix[2][2]};
  m_right = glm::vec3{m_viewMatrix[0][0], m_viewMatrix[0][1], m_viewMatrix[0][2]};
  m_up = glm::vec3{m_viewMatrix[1][0], m_viewMatrix[1][1], m_viewMatrix[1][2]};

  float yaw = m_rotation.y;
  m_forward = {sin(yaw), 0.f, cos(yaw)};
  m_right = {m_forward.z, 0.f, -m_forward.x};

  // Todo：提取相机所看向的方向
  m_lookAt = -glm::normalize(glm::vec3(m_viewMatrix[0][2], m_viewMatrix[1][2], m_viewMatrix[2][2]));
}

void Camera::RecalculateProjectionMatrix()
{
  m_projectionMatrix = glm::perspective(
      glm::radians(m_FOV),
      m_aspectRatio,
      m_nearClip,
      m_farClip);
  m_viewProjectionMatrix = m_projectionMatrix * m_viewMatrix;
}